package lua

//lua5.3 opcode
const (
	OP_MOVE     = iota /*A B R(A) := R(B)*/
	OP_LOADK           /*A Bx R(A) := Kst(Bx)*/
	OP_LOADKX          /*A R(A) := Kst(extra arg)*/
	OP_LOADBOOL        /*A B C R(A):=(Bool)B; if(C) pc++*/
	OP_LOADNIL         /*A B R(A),R(A+1),...,R(A+B) = nil*/
	OP_GETUPVAL        /*A B R(A):=UpValue[B]*/

	OP_GETTABUP /*A B C R(A):=UpValue[B][RK(C)]*/
	OP_GETTABLE /*A B C R(A):=R(B)[RK(C)]*/

	OP_SETTABUP /*A B C UpValue[A][RK(B)]:=RK(C)*/
	OP_SETUPVAL /*A B UpValue[B]:=R(A)*/
	OP_SETTABLE /*A B C R(A)[RK(B)]:=RK(C)*/

	OP_NEWTABLE /*A B C R(A) := {}(size=B,C)*/

	OP_SELF /*A B C R(A+1):=R(B);R(A):=R(B)[RK(C)]*/

	OP_ADD  /*A B C R(A):=RK(B) + RK(C)*/
	OP_SUB  /*A B C R(A):=RK(B) - RK(C)*/
	OP_MUL  /*A B C R(A):=RK(B) * RK(C)*/
	OP_MOD  /*A B C R(A):=RK(B) % RK(C)*/
	OP_POW  /*A B C R(A):=RK(B) ^ RK(C)*/
	OP_DIV  /*A B C R(A):=RK(B) / RK(C)*/
	OP_IDIV /*A B C R(A):=RK(B) //RK(C)*/
	OP_BAND /*A B C R(A):=RK(B) & RK(C)*/
	OP_BOR  /*A B C R(A):=RK(B) | RK(C)*/
	OP_BXOR /*A B C R(A):=RK(B) ~ RK(C)*/
	OP_SHL  /*A B C R(A):=RK(B) << RK(C)*/
	OP_SHR  /*A B C R(A):=RK(B) >> RK(C)*/
	OP_UNM  /*A B R(A):=-R(B)*/
	OP_BNOT /*A B R(A):=~R(B)*/
	OP_NOT  /*A B R(A):=not R(B)*/
	OP_LEN  /*A B R(A):= length of R(B)*/

	OP_CONCAT /*A B C R(A):=R(B).. ... ..R(C)*/

	OP_JMP /*A sBx pc+=sBx; if (A) close all upvalues >= R(A-1)*/
	OP_EQ  /*A B C if((RK(B) == RK(C)) ~= A) then pc++ */
	OP_LT  /*A B C if((RK(B) < RK(C)) ~= A) then pc++ */
	OP_LE  /*A B C if((RK(B) <= RK(C)) ~= A) then pc++ */

	OP_TEST    /*A C if not (R(A)<=>C) then pc++*/
	OP_TESTSET /*A B C if (R(B)<=>C) then R(A):=R(B) else pc++*/

	OP_CALL     /*A B C R(A),...,R(A+C-2) = R(A)(R(A+1),...,R(A+B-1))*/
	OP_TAILCALL /*A B C return R(A)(R(A+1),...,R(A+B-1))*/
	OP_RETURN   /*A B return R(A),...,R(A+B-2)*/

	OP_FORLOOP /*A sBx R(A)+=R(A+2);
	if R(A)<?= R(A+1) then {pc+=sBx;R(A+3)=R(A)*/
	OP_FORPREP /*A sBx R(A)-=R(A+2);pc+=sBx*/

	OP_TFORCALL /*A C R(A+3),...,R(A+2+C):=R(A)(R(A+1),R(A+2));*/
	OP_TFORLOOP /*A sBx if R(A+1)~=nil then { R(A)=R(A+1);pc+=sBx}*/

	OP_SETLIST /*A B C R(A)[(C-1)*FPF+i]:=R(A+i),1<=i<=B*/

	OP_CLOSURE /*A Bx R(A):=closure(KPROTO[Bx])*/

	OP_VARARG   /*A B R(A),R(A+1),...,R(A+B-2)=vararg*/
	OP_EXTRAARG /*Ax extra (larger) argument for previous opcode*/
)

const (
	SIZE_OP = 6
	SIZE_A  = 8
	SIZE_B  = 9
	SIZE_C  = 9
	SIZE_Bx = SIZE_C + SIZE_B
	SIZE_Ax = SIZE_C + SIZE_B + SIZE_A

	POS_OP = 0
	POS_A  = POS_OP + SIZE_OP
	POS_C  = POS_A + SIZE_A
	POS_B  = POS_C + SIZE_C
	POS_Bx = POS_C
	POS_Ax = POS_A

	MAXARG_A   = (1 << SIZE_A) - 1
	MAXARG_B   = (1 << SIZE_B) - 1
	MAXARG_C   = (1 << SIZE_C) - 1
	MAXARG_Bx  = (1 << SIZE_Bx) - 1
	MAXARG_Ax  = (1 << SIZE_Ax) - 1
	MAXARG_sBx = MAXARG_Bx >> 1
)

type Instruction uint32 //Instruction = B | C | A | OP

func mask1(n, p uint) Instruction { //n 1s from postion p
	return (^((^Instruction(0)) << n)) << p
}

func mask0(n, p uint) Instruction { //n 0s from position p
	return ^mask1(n, p)
}

func (i Instruction) GetOpCode() uint {
	return uint((i >> POS_OP) & mask1(SIZE_OP, 0))
}

func (i *Instruction) SetOpCode(o uint) {
	*i &= mask0(SIZE_OP, POS_OP)
	*i |= (Instruction(o) << POS_OP) & mask1(SIZE_OP, POS_OP)
}

func (i Instruction) getArg(pos, size uint) int {
	return int((i >> pos) & mask1(size, 0))
}

func (i *Instruction) setArg(v int, pos, size uint) {
	*i &= mask0(size, pos)
	*i |= (Instruction(v) << pos) & mask1(size, pos)
}

func (i Instruction) GetArgA() int {
	return i.getArg(POS_A, SIZE_A)
}

func (i *Instruction) SetArgA(v int) {
	i.setArg(v, POS_A, SIZE_A)
}

func (i Instruction) GetArgB() int {
	return i.getArg(POS_B, SIZE_B)
}

func (i *Instruction) SetArgB(v int) {
	i.setArg(v, POS_B, SIZE_B)
}

func (i Instruction) GetArgC() int {
	return i.getArg(POS_C, SIZE_C)
}

func (i *Instruction) SetArgC(v int) {
	i.setArg(v, POS_C, SIZE_C)
}

func (i Instruction) GetArgAx() int {
	return i.getArg(POS_Ax, SIZE_Ax)
}

func (i *Instruction) SetArgAx(v int) {
	i.setArg(v, POS_Ax, SIZE_Ax)
}

func (i Instruction) GetArgBx() int {
	return i.getArg(POS_Bx, SIZE_Bx)
}

func (i *Instruction) SetArgBx(v int) {
	i.setArg(v, POS_Bx, SIZE_Bx)
}

func (i Instruction) GetArgsBx() int {
	return i.GetArgBx() - MAXARG_sBx
}

func (i *Instruction) SetArgsBx(v int) {
	i.SetArgBx(v + MAXARG_sBx)
}

//Instruction Constructors
func CreateABC(o uint, a, b, c int) Instruction {
	return (Instruction(o) << POS_OP) |
		(Instruction(a) << POS_A) |
		(Instruction(b) << POS_B) |
		(Instruction(c) << POS_C)
}

func CreateABx(o uint, a, bc int) Instruction {
	return (Instruction(o) << POS_OP) |
		(Instruction(a) << POS_A) |
		(Instruction(bc) << POS_Bx)
}

func CreateAx(o uint, abc int) Instruction {
	return (Instruction(o) << POS_OP) |
		(Instruction(abc) << POS_Ax)
}
